package com.p7.architect.store.shoppingcart.infrastructure.util;

import cn.hutool.core.map.MapUtil;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.p7.architect.store.shoppingcart.domain.model.shoppingcart.ShoppingItem;
import com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl.diff.*;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class DiffUtils {

    public static <T> EntityDiff diff(T snapshot, T object) throws IllegalAccessException {
        Field[] fields = snapshot.getClass().getDeclaredFields();
        EntityDiff diff = new EntityDiff();
        Object oldValue, newValue;

        for (Field field : fields) {
            field.setAccessible(true);
            oldValue = field.get(snapshot);
            newValue = field.get(object);

            Diff diffAct = doDiff(field.getType(), oldValue, newValue);
            if (!Objects.isNull(diffAct)) {
                diff.addDiff(field.getName(), diffAct);
            }
        }

        if (!diff.getDiffs().isEmpty()) {
            diff.setSelfModified(true);
        }

        return diff;
    }

    private static Diff doDiff(Class<?> type, Object oldObj, Object newObj) {
        if (Objects.isNull(oldObj) || Objects.isNull(newObj)) {
            return null;
        }

        if (type.equals(List.class)) {
            return doListDiff((List) oldObj, (List) newObj);
        }
        if (type.equals(Map.class)) {
            return doMapDiff((Map) oldObj, (Map) newObj);
        }
        if (isDiff(oldObj, newObj)) {
            return new SingleDiff(oldObj, newObj);
        }
        return null;
    }

    private static Diff doListDiff(List<?> oldObj, List<?> newObj) {
        ListDiff diffs = new ListDiff();
        return diffs.size() > 0 ? diffs : null;
    }


    public static Diff doMapDiff(Map<?, ?> oldObj, Map<?, ?> newObj) {
        ListDiff diffs = new ListDiff();
        List<?> oldObjKeyList = new ArrayList<>(oldObj.keySet());
        List<?> newObjKeyList = new ArrayList<>(newObj.keySet());

        boolean shoppingItemFlag = false;

        // ShoppingItem entity 根据skuId来做唯一
        // itemNum不同的话应该要修改
        for (Object key : oldObjKeyList) {
            if (!newObjKeyList.contains(key)) {
                continue;
            }
            Object obj = newObj.get(key);
            if (!(obj instanceof ShoppingItem)) {
                continue;
            }
            shoppingItemFlag = true;
            ShoppingItem newObjValue = (ShoppingItem) newObj.get(key);
            ShoppingItem oldObjValue = (ShoppingItem) oldObj.get(key);
            if (!oldObjValue.getItemNum().equals(newObjValue.getItemNum())) {
                diffs.addDiff(new SingleDiff(null, newObjValue, DiffType.MODIFIED.getValue()));
            }
        }

        MapDifference<Object, Object> difference = Maps.difference(newObj, oldObj);
        Map<Object, Object> insertMap = difference.entriesOnlyOnLeft();
        if (MapUtil.isNotEmpty(insertMap)) {
            for (Map.Entry<Object, Object> map : insertMap.entrySet()) {
                Object value = map.getValue();
                diffs.addDiff(new SingleDiff(null, value, DiffType.ADDED.getValue()));
            }
        }

        Map<Object, Object> deleteMap = difference.entriesOnlyOnRight();
        if (MapUtil.isNotEmpty(deleteMap)) {
            for (Map.Entry<Object, Object> map : deleteMap.entrySet()) {
                Object value = map.getValue();
                diffs.addDiff(new SingleDiff(value, null, DiffType.DELETED.getValue()));
            }
        }

        if (shoppingItemFlag) {
            Map<Object, MapDifference.ValueDifference<Object>> updateMap = difference.entriesDiffering();
            if (MapUtil.isNotEmpty(updateMap)) {
                for (MapDifference.ValueDifference<Object> obj : updateMap.values()) {
                    diffs.addDiff(new SingleDiff(null, obj.leftValue(), DiffType.MODIFIED.getValue()));
                }
            }
        }
        return diffs.size() > 0 ? diffs : null;
    }


    private static boolean isDiff(Object oldObj, Object newObj) {
        if (Objects.isNull(oldObj) || Objects.isNull(newObj)) {
            return false;
        }
        return !oldObj.toString().equals(newObj.toString());
    }
}
